133 lines · 6.4 KB
1 ---
2 import Repo from '../../../../layouts/Repo.astro';
3 import { apiGet } from '../../../../lib/api';
4
5 const { owner, repo, sha } = Astro.params;
6 const cookie = Astro.request.headers.get('cookie') || '';
7
8 let commit: any = null;
9 let diff: any[] = [];
10 let error = '';
11
12 try {
13 [commit, diff] = await Promise.all([
14 apiGet(`/api/repos/${owner}/${repo}/commit/${sha}`, cookie),
15 apiGet(`/api/repos/${owner}/${repo}/commit/${sha}/diff`, cookie),
16 ]);
17 } catch (e: any) {
18 error = e.message;
19 }
20
21 function parseAuthor(author: string) {
22 const match = author?.match(/^(.+?) <(.+?)> (\d+) ([+-]\d{4})$/);
23 if (!match) return { name: author || 'unknown', email: '', date: '' };
24 const date = new Date(parseInt(match[3]) * 1000);
25 return {
26 name: match[1],
27 email: match[2],
28 date: date.toLocaleDateString('en-US', {
29 year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit',
30 }),
31 };
32 }
33
34 const stats = diff.reduce((acc: any, f: any) => {
35 const adds = f.hunks?.reduce((s: number, h: any) => s + h.lines.filter((l: any) => l.type === 'add').length, 0) ?? 0;
36 const dels = f.hunks?.reduce((s: number, h: any) => s + h.lines.filter((l: any) => l.type === 'delete').length, 0) ?? 0;
37 return { adds: acc.adds + adds, dels: acc.dels + dels };
38 }, { adds: 0, dels: 0 });
39 ---
40
41 <Repo owner={owner!} repo={repo!} activeTab="commits">
42 {error && <div class="flash-error">{error}</div>}
43
44 {commit && (() => {
45 const { name, email, date } = parseAuthor(commit.author);
46 const firstLine = commit.message.split('\n')[0];
47 const rest = commit.message.split('\n').slice(1).join('\n').trim();
48 return (
49 <div>
50 <div class="card" style="margin-bottom: 20px;">
51 <h2 style="font-size: 1.125rem; margin-bottom: 4px;">{firstLine}</h2>
52 {rest && (
53 <pre style="color: var(--text-muted); font-size: 0.8125rem; margin-bottom: 8px; white-space: pre-wrap; font-family: var(--font-sans);">{rest}</pre>
54 )}
55 <div style="font-size: 0.8125rem; color: var(--text-muted); border-top: 1px solid var(--border); padding-top: 8px; display: flex; justify-content: space-between;">
56 <span><strong style="color: var(--text);">{name}</strong> committed on {date}</span>
57 <code>{commit.sha.slice(0, 10)}</code>
58 </div>
59 {commit.parents.length > 0 && (
60 <div style="font-size: 0.8125rem; color: var(--text-muted); margin-top: 4px;">
61 {commit.parents.length === 1 ? 'Parent: ' : 'Parents: '}
62 {commit.parents.map((p: string, i: number) => (
63 <>{i > 0 && ', '}<a href={`/${owner}/${repo}/commit/${p}`}><code>{p.slice(0, 7)}</code></a></>
64 ))}
65 </div>
66 )}
67 </div>
68
69 {/* Diff stats */}
70 <div style="margin-bottom: 12px; font-size: 0.875rem; color: var(--text-muted);">
71 Showing <strong style="color: var(--text);">{diff.length}</strong> changed file{diff.length !== 1 ? 's' : ''} with
72 <strong style="color: var(--diff-add-text);"> {stats.adds} addition{stats.adds !== 1 ? 's' : ''}</strong> and
73 <strong style="color: var(--diff-del-text);"> {stats.dels} deletion{stats.dels !== 1 ? 's' : ''}</strong>
74 </div>
75
76 {/* File diffs */}
77 {diff.map((file: any) => (
78 <div class="card" style="margin-bottom: 8px; padding: 0; overflow: hidden;">
79 <div style="padding: 8px 16px; background: var(--bg-tertiary); border-bottom: 1px solid var(--border); font-size: 0.8125rem; display: flex; gap: 8px; align-items: center;">
80 <span style={`padding: 0 6px; border-radius: 4px; font-size: 0.75rem; font-weight: 600; ${
81 file.status === 'added' ? 'background: var(--diff-add-badge-bg); color: var(--diff-add-text);'
82 : file.status === 'deleted' ? 'background: var(--diff-del-badge-bg); color: var(--diff-del-text);'
83 : 'background: var(--diff-mod-badge-bg); color: var(--diff-mod-text);'
84 }`}>
85 {file.status === 'added' ? 'A' : file.status === 'deleted' ? 'D' : 'M'}
86 </span>
87 <span style="font-family: var(--font-mono);">{file.path}</span>
88 </div>
89 {file.isBinary ? (
90 <div style="padding: 16px; text-align: center; color: var(--text-muted);">Binary file</div>
91 ) : (
92 <div style="overflow-x: auto;">
93 {file.hunks?.map((hunk: any) => (
94 <table style="width: 100%; border-collapse: collapse; font-family: var(--font-mono); font-size: 0.8125rem;">
95 <tbody>
96 <tr>
97 <td colspan="3" style="padding: 4px 12px; background: var(--diff-hunk-bg); color: var(--diff-hunk-text); font-size: 0.75rem;">
98 @@ -{hunk.oldStart},{hunk.oldCount} +{hunk.newStart},{hunk.newCount} @@
99 </td>
100 </tr>
101 {hunk.lines.map((line: any) => (
102 <tr style={
103 line.type === 'add' ? 'background: var(--diff-add-bg);'
104 : line.type === 'delete' ? 'background: var(--diff-del-bg);'
105 : ''
106 }>
107 <td style="padding: 0 8px; text-align: right; color: var(--text-muted); user-select: none; width: 1%; white-space: nowrap; border-right: 1px solid var(--border);">
108 {line.oldLineNo ?? ''}
109 </td>
110 <td style="padding: 0 8px; text-align: right; color: var(--text-muted); user-select: none; width: 1%; white-space: nowrap; border-right: 1px solid var(--border);">
111 {line.newLineNo ?? ''}
112 </td>
113 <td style="padding: 0 12px; white-space: pre;">
114 <span style={
115 line.type === 'add' ? 'color: var(--diff-add-text);'
116 : line.type === 'delete' ? 'color: var(--diff-del-text);'
117 : ''
118 }>{line.type === 'add' ? '+' : line.type === 'delete' ? '-' : ' '}{line.content}</span>
119 </td>
120 </tr>
121 ))}
122 </tbody>
123 </table>
124 ))}
125 </div>
126 )}
127 </div>
128 ))}
129 </div>
130 );
131 })()}
132 </Repo>
133